#include "item/ellipticalarcitem.h"

#include <QtMath>
#include <QStyleOptionGraphicsItem>
#include <QPainter>

namespace SymbolEditor
{

    GraphicsEllipticalArcItem::GraphicsEllipticalArcItem(GraphicsItem *parent):
        GraphicsItem(parent),
        m_xRadius(0.0), m_yRadius(0.0),
        m_startAngle(0.0), m_spanAngle(360.0)
    {
        addHandles();
    }

    GraphicsEllipticalArcItem::~GraphicsEllipticalArcItem()
    {

    }

    qreal GraphicsEllipticalArcItem::xRadius() const
    {
        return m_xRadius;
    }

    qreal GraphicsEllipticalArcItem::yRadius() const
    {
        return m_yRadius;
    }

    qreal GraphicsEllipticalArcItem::startAngle() const
    {
        return m_startAngle;
    }

    qreal GraphicsEllipticalArcItem::spanAngle() const
    {
        return m_spanAngle;
    }

    void GraphicsEllipticalArcItem::setXRadius(qreal r)
    {
        qreal radius = qAbs(r);
        if (qFuzzyCompare(radius, m_xRadius))
        {
            return;
        }
        prepareGeometryChange();
        m_boundingRect = QRectF();
        m_xRadius = radius;
        update();
        updateHandles();
    }

    void GraphicsEllipticalArcItem::setYRadius(qreal r)
    {
        qreal radius = qAbs(r);
        if (qFuzzyCompare(radius, m_yRadius))
        {
            return;
        }
        prepareGeometryChange();
        m_boundingRect = QRectF();
        m_yRadius = radius;
        update();
        updateHandles();
    }

    void GraphicsEllipticalArcItem::setStartAngle(qreal a)
    {
        qreal angle = fmod(a, 360);
        if (qFuzzyCompare(angle, m_startAngle))
        {
            return;
        }
        prepareGeometryChange();
        m_boundingRect = QRectF();
        m_startAngle = angle;
        update();
        updateHandles();
    }

    void GraphicsEllipticalArcItem::setSpanAngle(qreal a)
    {
        qreal angle = fmod(a, 360);
        if (qFuzzyCompare(angle, m_spanAngle))
        {
            return;
        }
        prepareGeometryChange();
        m_boundingRect = QRectF();
        m_spanAngle = angle;
        update();
        updateHandles();
    }

    void GraphicsEllipticalArcItem::addHandles()
    {
        addHandle(XRadiusHandle, Handle::Role::Move, Handle::Shape::Diamond);
        addHandle(YRadiusHandle, Handle::Role::Move, Handle::Shape::Diamond);
        addHandle(StartAngleHandle, Handle::Role::Move, Handle::Shape::Circle);
        addHandle(SpanAngleHandle, Handle::Role::Move, Handle::Shape::Circle);
    }

    void GraphicsEllipticalArcItem::updateHandles()
    {
        blockItemNotification();
        handleAt(XRadiusHandle)->setPos(QPointF(m_xRadius, 0.0));
        handleAt(YRadiusHandle)->setPos(QPointF(0.0, m_yRadius));
        handleAt(StartAngleHandle)->setPos(pointAt(m_startAngle));
        handleAt(SpanAngleHandle)->setPos(pointAt(m_startAngle + m_spanAngle));
        unblockItemNotification();
    }

    QPointF GraphicsEllipticalArcItem::pointAt(qreal angle) const
    {
        qreal theta = qDegreesToRadians(angle);
        return QPointF(m_xRadius * qCos(theta), -m_yRadius * qSin(theta));
    }

    qreal GraphicsEllipticalArcItem::angleAt(const QPointF &pos) const
    {
        QLineF vector(QPointF(0, 0), QPointF(pos.x() / xRadius(), pos.y() / m_yRadius));
        return vector.angle();
    }

    QRectF GraphicsEllipticalArcItem::rect() const
    {
        return QRectF(-m_xRadius, -m_yRadius, m_xRadius * 2.0, m_yRadius * 2.0);
    }

    QRectF GraphicsEllipticalArcItem::boundingRect() const
    {
        if (m_boundingRect.isNull())
        {
            qreal pw = pen().style() == Qt::NoPen ? qreal(0) : pen().widthF();
            if (pw == 0.0 && qFuzzyCompare(m_spanAngle, 360))
            {
                m_boundingRect = rect();
            }
            else
            {
                m_boundingRect = shape().controlPointRect();
            }
        }
        return m_boundingRect;
    }

    QPainterPath GraphicsEllipticalArcItem::shape() const
    {
        QPainterPath path;
        if (qFuzzyCompare(m_spanAngle, 360.0))
        {
            path.addEllipse(rect());
        }
        else
        {
            path.arcTo(rect(), m_startAngle, m_spanAngle);
        }

        return shapeFromPath(path, pen());
    }

    void GraphicsEllipticalArcItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *option,
                        QWidget *widget)
    {
        Q_UNUSED(option);
        Q_UNUSED(widget);
        painter->setPen(pen());
        painter->setBrush(brush());
        if (qFuzzyCompare(m_spanAngle, 360))
        {
            painter->drawEllipse(rect());
        }
        else
        {
            painter->drawPie(rect(), qRound(m_startAngle * 16), qRound(m_spanAngle * 16));
        }
    }

    QVariant GraphicsEllipticalArcItem::itemChange(QGraphicsItem::GraphicsItemChange change,
                                 const QVariant &value)
    {
        if (change == QGraphicsItem::ItemSelectedHasChanged)
        {
            for (int i = 0; i < handleCount(); i++)
            {
                handleAt(i)->setVisible(isSelected());
            }
        }
        return value;
    }

    void GraphicsEllipticalArcItem::itemNotification(IObservableItem *item)
    {
        Handle *handle = static_cast<Handle *>(item);
        qreal angle = angleAt(handle->pos());
        switch (handle->handleId())
        {
            case XRadiusHandle:
                setXRadius(handle->x());
                break;
            case YRadiusHandle:
                setYRadius(handle->y());
                break;
            case StartAngleHandle:
                setStartAngle(angle);
                break;
            case SpanAngleHandle:
                setSpanAngle(angle + 360 - m_startAngle);
                break;
            default:
                Q_ASSERT(false);
                break;
        }
    }

    GraphicsItem *GraphicsEllipticalArcItem::clone()
    {
        GraphicsEllipticalArcItem *item = new GraphicsEllipticalArcItem();
        item->setXRadius(m_xRadius);
        item->setYRadius(m_yRadius);
        item->setStartAngle(m_startAngle);
        item->setSpanAngle(m_spanAngle);
        GraphicsItem::cloneTo(item);
        return item;
    }

    void GraphicsEllipticalArcItem::setProperty(PropertyId id, const QVariant &value)
    {
        switch (id)
        {
            case XRadiusProperty:
                setXRadius(value.toReal());
                break;
            case YRadiusProperty:
                setYRadius(value.toReal());
                break;
            case StartAngleProperty:
                setStartAngle(value.toReal());
                break;
            case SpanAngleProperty:
                setSpanAngle(value.toReal());
                break;
            default:
                GraphicsItem::setProperty(id, value);
                break;
        }
    }

    QList<QPointF> GraphicsEllipticalArcItem::endPoints() const
    {
        return QList<QPointF>() << pointAt(m_startAngle) << pointAt(m_startAngle + m_spanAngle);
    }

    QList<QPointF> GraphicsEllipticalArcItem::midPoints() const
    {
        return QList<QPointF>() << pointAt(m_startAngle + m_spanAngle / 2.0);
    }

    QList<QPointF> GraphicsEllipticalArcItem::centerPoints() const
    {
        return QList<QPointF>() << QPointF(0, 0);
    }

    // FIXME: See static helpers in Item
    QList<QPointF> GraphicsEllipticalArcItem::nearestPoints(QPointF pos) const
    {
        QPointF point = mapFromScene(pos);
        qreal stopAngle = fmod(m_startAngle + m_spanAngle, 360.0);
        qreal theta = angleAt(point);
        QList<QPointF> candidates;

        if (m_startAngle < theta && theta < stopAngle)
        {
            return candidates << pointAt(theta);
        }

        QLineF edge1(QPointF(0, 0), pointAt(m_startAngle));
        candidates << nearestPoint(edge1, point);
        QLineF edge2(QPointF(0, 0), pointAt(stopAngle));
        candidates << nearestPoint(edge2, point);

        QPointF nearest(qInf(), qInf());
        for (auto candidate: candidates)
        {
            if ((point - candidate).manhattanLength() < (point - nearest).manhattanLength())
            {
                nearest = candidate;
            }
        }
        return QList<QPointF>() << nearest;
    }

}
